---------------Wiziprint---------------
A 4am crack                  2020-01-02
---------------------------------------

Name: Wiziprint
Version: 2.01 (20-AUG-83 according to
  file metadata)
Genre: game utility
Year: 1982
Credits: Andrew Greenberg, Robert
  Woodhead
Publisher: Sir-Tech Software
Platform: Apple ][+ or later
Media: 5.25-inch disk
Sides: 1
OS: Apple Pascal
Previous cracks: none

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  fails on last pass

Locksmith Fast Disk Backup
  copies almost the entire disk except
  a few curious sectors on track $22

                 --v--

     LOCKSMITH 7.0  FAST DISK BACKUP

   R..................................*
   W
HEX 00000000000000001111111111111111222
TRK 0123456789ABCDEF0123456789ABCDEF012
   0..................................A
   1..................................D
   2...................................
   3...................................
   4...................................
   5...................................
   6...................................
   7...................................
   8..................................A
   9...................................
   A...................................
   B...................................
   C...................................
   D...................................
12 E...................................
   F..................................D

                 --^--

  This is especially curious because
  two of them failed differently than
  the other two. "A" means that
  Locksmith can not find or read the
  address field. "D" means that it
  can not find or read the data field.
  So there's definitely something funky
  going on on track $22.

  Anyway, the copy boots to the title
  screen then freezes.

EDD 4 bit copy (no sync, no count)
  no errors, but same behavior as the
  Locksmith FDB copy: it boots to the
  title screen then freezes.

Copy ][+ nibble editor
  looking through track $22, obviously,
  and I find a curious sector-but-not-
  a-sector which might explain the
  status result I saw on Locksmith FDB.
  I've replaced the inverse characters,
  which represent timing bits, with a
  "+" character for the purposes of
  plain text.

                 --v--

TRACK: 22  START: 1E1A  LENGTH: 180F

3220: D5 AA 96 AA AA BB AA AE   VIEW
3228: AF BF AF DE AA EA+FF+FF+
3230: FE FF+FF+FF+FF+D5 AA AD
3238: 96 FF+FF+FF+FF+FF+FF+FF+
3240: FF+FF+FF+FF+FF+FF+FF+FF+ <-3240
3248: FF+FF+FF+FF+FF+FF+FF+FF+
3250: FF+FF+FF+FF+FF+FF+FF+FF+
3258: FF+FF+FF+FF+FF+FF+FF+FF+ FIND:
3260: FF+FF+FF+FF+FF+FF+FF+FF+ D5 AA 96

                 --^--

  It goes on like that for a while.

Disk Fixer
  bootloader is Pascal, which is also
  obvious from watching the disk boot
  (Apple Pascal has a distinctive look
  and sound), but no help on track $22

Why didn't COPYA work?
  non-sectors on track $22

Why didn't Locksmith FDB work?
  likewise, and there must be a run-
  time check that is looking at that
  bitstream inside the sector-that-
  isn't-a-sector on track $22

Why didn't my EDD copy work?
  likewise

Next steps:

  1. Find the protection check, which
     I really hope is implemented in
     assembly language and not Pascal's
     weird interpreted p-code
  2. Disable protection check
  3. Declare victory (*)

(*) go to the gym

                   ~

               Chapter 1
           Unlucky In Cards


Since my copy goes down a different
code path than the original, I'm
guessing there is a runtime protection
check somewhere. One thing that all
protection checks have in common is
they need to turn on the drive motor by
accessing a specific address in the
$C0xx range. For slot 6, it's $C0E9,
but to allow disks to boot from any
slot, developers usually use code like
this:

  LDX <slot number x 16>
  LDA $C089,X

There's nothing that says where the
slot number has to be, although the
disk controller ROM routine uses zero
page $2B and lots of disks just reuse
that. There's also nothing that says
you have to use the X-register as the
index, or that you must use the
accumulator as the load register. But
most RWTS code does, out of convention
I suppose (or possibly fear of messing
up such low-level code in subtle ways).

Also, since developers don't actually
want people finding their protection-
related code, they may try to encrypt
it or obfuscate it to prevent people
from finding it. But eventually, the
code must exist and the code must run,
and it must run on my machine, and I
have the final say on what my machine
does or does not do.

But sometimes you get lucky.

Turning to my trusty Disk Fixer sector
editor, I search the non-working copy
for "BD 89 C0", which is the opcode
sequence for "LDA $C089,X".

[Disk Fixer]
  ["F"ind]
    ["H"ex]
      ["BD 89 C0"]

                 --v--

------------- DISK SEARCH -------------

$02/$05-$0F   $10/$0E-$0F   $15/$0B-$39
$18/$0B-$39

                 --^--

The first two matches are legitimate
parts of the Pascal operating system.
The other two sectors are identical,
which is somewhat concerning, but we'll
jump off that bridge when we come to
it.

T15,S0B
----------- DISASSEMBLY MODE ----------
002A:68             PLA
002B:85 00          STA   $00
002D:68             PLA
002E:85 01          STA   $01
0030:68             PLA
0031:68             PLA
0032:68             PLA
0033:68             PLA
0034:A9 00          LDA   #$00
0036:48             PHA

; hard-coded to slot 6
0037:A2 60          LDX   #$60

; turn on the drive motor
0039:BD 89 C0       LDA   $C089,X

; and now this
003C:20 2D 00       JSR   $002D
003F:CE FB 00       DEC   $00FB
0042:D0 F8          BNE   $003C

The main program is written in Pascal,
and this assembly language module is
linked into the program by the linker.
In linked modules, JSR targets are
stored as two-byte addresses relative
to the start of the module. At runtime,
the Pascal interpreter decides where
the linked module is going to be loaded
and modifies the JSR operands to real
addresses. (Other address, like the one
in the "DEC $00FB", are also adjusted.)
Which is kind of neat for an 8-bit
virtual machine.

Since this module started at byte $2A
in this sector, and $2A + $2D = $57, we
will find this subroutine at byte $57
in the same sector.

However, it occurs to me that it will
be much easier to follow this write-up
if I shift everything in the sector
editor by $2A bytes, so that the JSR
addresses line up with the byte offsets
and we can pretend that the relative
JSR "addresses" are actual addresses.

Thus...

T15,S0B, shifted $2A bytes to the left
----------- DISASSEMBLY MODE ----------
0000:68             PLA
0001:85 00          STA   $00
0003:68             PLA
0004:85 01          STA   $01
0006:68             PLA
0007:68             PLA
0008:68             PLA
0009:68             PLA
000A:A9 00          LDA   #$00
000C:48             PHA
000D:A2 60          LDX   #$60
000F:BD 89 C0       LDA   $C089,X
0012:20 2D 00       JSR   $002D
0015:CE FB 00       DEC   $00FB
0018:D0 F8          BNE   $0012

Now then, what's at $002D?

                   ~

               Chapter 2
            Proving Ground


; reset the data latch and match the
; standard $D5 $AA $96 address field
; prologue
002D:BD 8E C0       LDA   $C08E,X
0030:BD 8C C0       LDA   $C08C,X
0033:10 FB          BPL   $0030
0035:C9 D5          CMP   #$D5
0037:D0 F7          BNE   $0030
0039:BD 8C C0       LDA   $C08C,X
003C:10 FB          BPL   $0039
003E:C9 AA          CMP   #$AA
0040:D0 F3          BNE   $0035
0042:BD 8C C0       LDA   $C08C,X
0045:10 FB          BPL   $0042
0047:C9 96          CMP   #$96
0049:D0 EA          BNE   $0035

004B:20 88 00       JSR   $0088
...

; fetches two nibbles and combines them
; into one 4-and-4-encoded value, like
; a standard address field
0088:BD 8C C0       LDA   $C08C,X
008B:10 FB          BPL   $0088
008D:38             SEC
008E:2A             ROL
008F:8D 9B 00       STA   $009B
0092:BD 8C C0       LDA   $C08C,X
0095:10 FB          BPL   $0092
0097:2D 9B 00       AND   $009B
009A:60             RTS

Continuing from $004E...

; this will be the disk volume number
004E:85 02          STA   $02
0050:20 88 00       JSR   $0088

; oops, no, it looks like we're using
; zp$02 for a rolling checksum of the
; address field
0053:45 02          EOR   $02
0055:85 02          STA   $02
0057:20 88 00       JSR   $0088

; this will be the sector number
005A:8D FD 00       STA   $00FD

; update checksum
005D:45 02          EOR   $02
005F:85 02          STA   $02
0061:20 88 00       JSR   $0088

; update and verify checksum
0064:45 02          EOR   $02
0066:D0 C8          BNE   $0030

; standard address field prologue
; ($DE $AA)
0068:BD 8C C0       LDA   $C08C,X
006B:10 FB          BPL   $0068
006D:C9 DE          CMP   #$DE
006F:D0 C4          BNE   $0035
0071:BD 8C C0       LDA   $C08C,X
0074:10 FB          BPL   $0071
0076:C9 AA          CMP   #$AA
0078:D0 BB          BNE   $0035
007A:60             RTS

Nothing super interesting yet. The
subroutine finds the next available
sector, parses out the sector number
from the address field, and saves it.

The caller has some kind of DEC/BNE
loop based on the value of $00FD,
another relative address. Perhaps the
caller reads a specific "block" (in
Pascal terminology) that puts the drive
head on track $22, then in assembly
language we skip over a certain number
of sectors until we get to the copy
protection pseudo-sectors? Not sure.

Continuing from the caller at $001A...

001A:20 DE 00       JSR   $00DE

; load value #1, call a thing, check
; the result
00DE:AD FE 00       LDA   $00FE
00E1:20 9C 00       JSR   $009C
00E4:C9 10          CMP   #$10
00E6:90 11          BCC   $00F9
00E8:C9 1C          CMP   #$1C
00EA:B0 0D          BCS   $00F9

; load value #2, call a thing, check
; the result
00EC:AD FF 00       LDA   $00FF
00EF:20 9C 00       JSR   $009C
00F2:C9 26          CMP   #$26
00F4:90 03          BCC   $00F9
00F6:C9 36          CMP   #$36

; return with carry clear (success)
00F8:60             RTS

; return with carry set (failure)
00F9:38             SEC
00FA:60             RTS

I think this is the heart of the copy
protection. We're taking two different
values and calling the subroutine at
$009C, checking that the return value
(in the accumulator) is within a
certain range, and returning success
(carry clear) or failure (carry set).

Here are the values in $00FE and $00FF:

00FE:0D
00FF:0F

Possibly sector numbers? Let's see
what's at $009C.

                   ~

               Chapter 3
      The Heart of the Maelstrom


009C:20 7B 00       JSR   $007B
...

; save the value we passed in
007B:85 03          STA   $03

; find the next address field and
; save the sector number
007D:20 2D 00       JSR   $002D

; check if the sector number matches
; the one we passed in
0080:AD FD 00       LDA   $00FD
0083:C5 03          CMP   $03

; otherwise loop until we find it
0085:D0 F6          BNE   $007D
0087:60             RTS

OK, this is becoming clear. The earlier
calls to $002D were just to check that
all the sectors exist. Now we're
looking for specific sectors ($0D and
$0F, the values stored in relative
addresses $00FE and $00FF).

Once we find a given sector, what do
we do with it? Continuing from $009F...

009F:20 BF 00       JSR   $00BF
...

; find the data field prologue
00BF:BD 8E C0       LDA   $C08E,X
00C2:BD 8C C0       LDA   $C08C,X
00C5:10 FB          BPL   $00C2
00C7:C9 D5          CMP   #$D5
00C9:D0 F7          BNE   $00C2
00CB:BD 8C C0       LDA   $C08C,X
00CE:10 FB          BPL   $00CB
00D0:C9 AA          CMP   #$AA
00D2:D0 F3          BNE   $00C7
00D4:BD 8C C0       LDA   $C08C,X
00D7:10 FB          BPL   $00D4
00D9:C9 AD          CMP   #$AD
00DB:D0 EA          BNE   $00C7
00DD:60             RTS

Continuing from $00A2...

; $00FA contains an "RTS", so this is
; just burning CPU cycles (6 for JSR,
; 6 for RTS)
00A2:A0 00          LDY   #$00
00A4:20 FA 00       JSR   $00FA
00A7:20 FA 00       JSR   $00FA
00AA:20 FA 00       JSR   $00FA
00AD:88             DEY
00AE:D0 F4          BNE   $00A4

; reset data latch
00B0:BD 8E C0       LDA   $C08E,X

; count nibbles
00B3:BD 8C C0       LDA   $C08C,X
00B6:10 FB          BPL   $00B3
00B8:C8             INY

; until data field epilogue
00B9:C9 DE          CMP   #$DE
00BB:D0 F6          BNE   $00B3

; nibble count is in Y, move it to A
; and return to caller
00BD:98             TYA
00BE:60             RTS

That's it, that's the protection. A
nibble count of two different regions
on track $22. Looking again at the
caller ($00DE), the relative addresses
$00FE and $00FF contain the sector
numbers of the fake sectors that really
contain the protection nibbles. We call
the count routine ($009C), which finds
the given sector ($007D) then counts
nibbles, then we return to here and
check the nibble count result:

00DE:AD FE 00       LDA   $00FE
00E1:20 9C 00       JSR   $009C
00E4:C9 10          CMP   #$10  \
00E6:90 11          BCC   $00F9  |
00E8:C9 1C          CMP   #$1C   |
00EA:B0 0D          BCS   $00F9 /
00EC:AD FF 00       LDA   $00FF
00EF:20 9C 00       JSR   $009C
00F2:C9 26          CMP   #$26  \
00F4:90 03          BCC   $00F9  |
00F6:C9 36          CMP   #$36   |
00F8:60             RTS         /
00F9:38             SEC
00FA:60             RTS

This routine returns with a simple yes
or no answer, which takes us all the
way back to [...checks notes...] $001A.

; protection routine returns carry
; clear on success, set on failure
001A:20 DE 00       JSR   $00DE

; turn off drive motor (does not affect
; carry bit)
001D:BD 88 C0       LDA   $C088,X

; shift carry bit into bit 0
; success -> bit 0 = 0
; failure -> bit 0 = 1
0020:2A             ROL

; invert
; success -> bit 0 = 1
; failure -> bit 0 = 0
0021:49 FF          EOR   #$FF

; ignore everything but bit 0
; success -> A = #$01
; failure -> A = #$00
0023:29 01          AND   #$01

; push this value to the stack and
; return to the main Pascal program
0025:48             PHA
0026:A5 01          LDA   $01
0028:48             PHA
0029:A5 00          LDA   $00
002B:48             PHA
002C:60             RTS

If we want this routine to always claim
success, we need to push #$01 to the
stack at $0025. The easiest way to
ensure that is to change the "AND #$01"
at $0023 to "LDA #$01".

T15,S0B,$4D: 29 -> A9

(Aficianados of minimalism will
appreciate that $29 and $A9 differ only
in bit 7, making this a 1-bit crack.)

Some disk analysis shows that this
sector is part of the SYSTEM.STARTUP
program, which makes sense. (It's like
the HELLO program of Pascal.) Further
analysis shows that T18,S0B is unused.
Given the way Pascal works, it's likely
a copy of a previous build. Perhaps a
candidate for future study, but the
crack is complete.

Quod erat liberandum.

---------------------------------------
A 4am crack                    No. 2137
------------------EOF------------------
